﻿namespace SolidMvvm {
	using System;
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using System.Drawing;
	using System.Windows;
	using System.Windows.Forms;
	using System.Windows.Media;
	using System.Windows.Media.Imaging;
	using System.Windows.Shapes;

	using SolidMvvmInterfaces;

	// TODO: Fix issue where it saves window as minimized with -32000,-32000 left/top
	public class WPFWindow : PropertyChangedNotifier, IDisposable {
		readonly NotifyIcon notifyIconOrNull;

		public WPFWindow(string title, ISettingDictionary settingDictionary, System.Windows.Size defaultSize, string pathAndIconFileNameWithExtensionEmbeddedAsResource, WindowState defaultWindowState, bool minimizingGoesToSystemTrayIcon, bool isWindowTransparent, WindowStyle windowStyleIfNotTransparent, ResizeMode resizeMode, bool sizeToContents, bool alwaysOnTop, Action<WPFWindow /* sender */> closeRequested) {
			PathAndIconFileNameWithExtensionEmbeddedAsResource = pathAndIconFileNameWithExtensionEmbeddedAsResource;
			SettingDictionary = settingDictionary;
			CloseRequested = closeRequested;
			DefaultSize = defaultSize;
			DefaultWindowState = defaultWindowState;
			AlwaysOnTop = alwaysOnTop;
			MinimizingGoesToSystemTrayIcon = minimizingGoesToSystemTrayIcon;
			SizeToContents = sizeToContents;
			IsWindowTransparent = isWindowTransparent;
			WindowStyleIfNotTransparent = windowStyleIfNotTransparent;
			ResizeMode = resizeMode;

			((WPFApp.InternalWPFApplication)System.Windows.Application.Current).Exiting += WPFWindow_Exiting;

			DefaultWindowState = settingDictionary.GetAsEnum("WindowState", WindowState.Normal);

			ImageSource applicationIcon = new BitmapImage(new Uri(PathAndIconFileNameWithExtensionEmbeddedAsResource));

			// The window state will default to normal. During the loaded event, we can maximize if needed.
			NativeWindow = new WindowDeluxe { AllowsTransparency = isWindowTransparent, WindowStyle = isWindowTransparent ? WindowStyle.None : windowStyleIfNotTransparent, WindowStartupLocation = WindowStartupLocation.Manual, Title = title, Icon = applicationIcon, Background = isWindowTransparent ? System.Windows.Media.Brushes.Transparent : null, Topmost = alwaysOnTop, ResizeMode = resizeMode };
			NativeWindow.Closing += NativeWindow_Closing;
			NativeWindow.Loaded += NativeWindow_Loaded;

			// Pay attention to the window being minimized in order to minimize to the tray.
			NativeWindow.StateChanged += NativeWindow_StateChanged;
			NativeWindow.SizeChanged += NativeWindow_SizeChanged;
			NativeWindow.LocationChanged += NativeWindow_LocationChanged;

			NativeWindow.SizeToContent = sizeToContents ? SizeToContent.WidthAndHeight : SizeToContent.Manual;

			if (minimizingGoesToSystemTrayIcon) {
				notifyIconOrNull = new NotifyIcon { Visible = false, Icon = new Icon(PathAndIconFileNameWithExtensionEmbeddedAsResource), BalloonTipText = title + " (Double-click to show interface.)" };
				// Limited to 60 characters.
				notifyIconOrNull.Text = notifyIconOrNull.BalloonTipText.Substring(0, Math.Min(60, notifyIconOrNull.BalloonTipText.Length));
				notifyIconOrNull.DoubleClick += NotifyIconOrNull_DoubleClick;
			}
		}

		public bool AlwaysOnTop { get; private set; }

		public Action<WPFWindow /* sender */> CloseRequested { get; private set; }

		public object ClientContent
		{
			get
			{
				return NativeWindow.ClientContent;
				//return dockPanel.Children.Count == 1 ? null : dockPanel.Children[1];
				//return NativeWindow.Content;
			}
			set
			{
				NativeWindow.ClientContent = value;
				//if (dockPanel.Children.Count == 2 && dockPanel.Children[1] == value)
				//	//if (NativeWindow.Content == value)
				//	return;

				//if (dockPanel.Children.Count == 2)
				//	dockPanel.Children.RemoveAt(1);
				//dockPanel.Children.Add(new ContentPresenter { Content = value });

				//NativeWindow.Content = value;
				RaisePropertyChanged(() => ClientContent);
			}
		}

		public System.Windows.Size DefaultSize { get; private set; }

		public WindowState DefaultWindowState { get; }

		public bool IsVisible
		{
			get
			{
				return NativeWindow.IsVisible;
			}
			set
			{
				if (NativeWindow.IsVisible == value)
					return;

				if (value)
					NativeWindow.Show();
				else
					NativeWindow.Hide();

				RaisePropertyChanged(() => IsVisible);
			}
		}

		public bool IsWindowTransparent { get; private set; }

		public bool MinimizingGoesToSystemTrayIcon { get; private set; }

		public WindowDeluxe NativeWindow { get; private set; }

		public string PathAndIconFileNameWithExtensionEmbeddedAsResource { get; private set; }

		public ResizeMode ResizeMode { get; private set; }

		public ISettingDictionary SettingDictionary { get; private set; }

		public bool SizeToContents { get; }

		public WindowStyle WindowStyleIfNotTransparent { get; private set; }

		public ObservableCollection<ISnapInPoint> LeftStatusBarItemSnapInPoints
		{
			get
			{
				return NativeWindow.LeftStatusBarItemSnapInPoints;
			}
		}

		public ObservableCollection<ISnapInPoint> CenterStatusBarItemSnapInPoints
		{
			get
			{
				return NativeWindow.CenterStatusBarItemSnapInPoints;
			}
		}

		public ObservableCollection<ISnapInPoint> RightStatusBarItemSnapInPoints
		{
			get
			{
				return NativeWindow.RightStatusBarItemSnapInPoints;
			}
		}

		public void Dispose() {
			if (NativeWindow != null) {
				NativeWindow.Closing -= NativeWindow_Closing;
				NativeWindow.Loaded -= NativeWindow_Loaded;
				NativeWindow.StateChanged -= NativeWindow_StateChanged;
				NativeWindow.SizeChanged -= NativeWindow_SizeChanged;
				NativeWindow.LocationChanged -= NativeWindow_LocationChanged;

				NativeWindow.Content = null;
				NativeWindow.Close();
			}

			PathAndIconFileNameWithExtensionEmbeddedAsResource = null;
			SettingDictionary = null;
			DefaultSize = System.Windows.Size.Empty;
			NativeWindow = null;
			CloseRequested = null;
		}

		void NativeWindow_Closing(object sender, CancelEventArgs e) {
			e.Cancel = true;

			CloseRequested(this);
		}

		void NativeWindow_Loaded(object sender, RoutedEventArgs e) {
			try {
				var workAreaOfPrimaryDisplay = SystemParameters.WorkArea;

				if (!SettingDictionary.ContainsKey("Left")) {
					NativeWindow.Width = Math.Min(DefaultSize.Width, workAreaOfPrimaryDisplay.Width);
					NativeWindow.Height = Math.Min(DefaultSize.Height, workAreaOfPrimaryDisplay.Height);

					// Center it on the primary display.
					NativeWindow.Left = (workAreaOfPrimaryDisplay.Width - DefaultSize.Width) / 2 + workAreaOfPrimaryDisplay.Left;
					NativeWindow.Top = (workAreaOfPrimaryDisplay.Height - DefaultSize.Height) / 2 + workAreaOfPrimaryDisplay.Top;
				}
				else {
					// Start by restoring the old bounds. We'll fix them later if needed (for example, if the user removed a display or something).
					var savedBounds = new Rect(SettingDictionary.GetAsDouble("Left", workAreaOfPrimaryDisplay.X), SettingDictionary.GetAsDouble("Top", workAreaOfPrimaryDisplay.Y), SettingDictionary.GetAsDouble("Width", workAreaOfPrimaryDisplay.Width), SettingDictionary.GetAsDouble("Height", workAreaOfPrimaryDisplay.Height));
					NativeWindow.Left = savedBounds.Left;
					NativeWindow.Top = savedBounds.Top;
					NativeWindow.Width = savedBounds.Width;
					NativeWindow.Height = savedBounds.Height;

					// This clever Winforms routine gives you a reference to the screen that mostly contains your app.
					var screenContainingMostOfTheWindow = Screen.FromRectangle(new System.Drawing.Rectangle((int)NativeWindow.Left, (int)NativeWindow.Top, (int)NativeWindow.Width, (int)NativeWindow.Height));

					// If we can adjust the width/height...
					if (!SizeToContents) {
						// If window is too big for the screen, we size to the width and/or height of the screen.
						NativeWindow.Width = Math.Min(NativeWindow.Width, screenContainingMostOfTheWindow.Bounds.Width);
						NativeWindow.Height = Math.Min(NativeWindow.Height, screenContainingMostOfTheWindow.Bounds.Height);
					}

					// We can adjust the left and top at this point, but not the width and height.
					if (NativeWindow.Left + NativeWindow.Width > screenContainingMostOfTheWindow.Bounds.Right)
						NativeWindow.Left = screenContainingMostOfTheWindow.Bounds.Right - NativeWindow.Width;
					if (NativeWindow.Top + NativeWindow.Height > screenContainingMostOfTheWindow.Bounds.Bottom)
						NativeWindow.Top = screenContainingMostOfTheWindow.Bounds.Bottom - NativeWindow.Height;

					// We may have over-adjusted if the window sizes to content, but doesn't actually fit in the screen.
					// Let's just be sure that the top-left of the window is visible so that the user can drag it back.
					if (NativeWindow.Left < screenContainingMostOfTheWindow.Bounds.Left)
						NativeWindow.Left = screenContainingMostOfTheWindow.Bounds.Left;
					if (NativeWindow.Top < screenContainingMostOfTheWindow.Bounds.Top)
						NativeWindow.Top = screenContainingMostOfTheWindow.Bounds.Top;
				}

				NativeWindow.WindowState = DefaultWindowState;
			}
			catch (Exception exception) {
				System.Windows.MessageBox.Show(exception.Message, "Start-Up Error", MessageBoxButton.OK, MessageBoxImage.Error);
				NativeWindow.Close();
			}
		}

		void NativeWindow_LocationChanged(object sender, EventArgs e) {
			SettingDictionary.SetAsDouble("Left", NativeWindow.Left);
			SettingDictionary.SetAsDouble("Top", NativeWindow.Top);
			SettingDictionary.Save();
		}

		void NativeWindow_SizeChanged(object sender, SizeChangedEventArgs e) {
			if (!double.IsNaN(NativeWindow.Width))
				SettingDictionary.SetAsDouble("Width", NativeWindow.Width);
			if (!double.IsNaN(NativeWindow.Height))
				SettingDictionary.SetAsDouble("Height", NativeWindow.Height);
			SettingDictionary.Save();
		}

		void NativeWindow_StateChanged(object sender, EventArgs e) {
			if (NativeWindow.WindowState == WindowState.Minimized) {
				if (notifyIconOrNull != null) {
					notifyIconOrNull.Visible = true;
					notifyIconOrNull.ShowBalloonTip(1000);
					NativeWindow.ShowInTaskbar = false;
				}
			}
			else if (NativeWindow.WindowState != WindowState.Minimized)
				if (notifyIconOrNull != null) {
					notifyIconOrNull.Visible = false;
					NativeWindow.ShowInTaskbar = true;
				}

			SettingDictionary.SetAsString("WindowState", NativeWindow.WindowState.ToString());
			SettingDictionary.Save();
		}

		void NotifyIconOrNull_DoubleClick(object sender, EventArgs e) {
			NativeWindow.WindowState = WindowState.Normal;
		}

		void WPFWindow_Exiting(WPFApp.InternalWPFApplication sender, EventArgs eventArgs) {
			((WPFApp.InternalWPFApplication)System.Windows.Application.Current).Exiting -= WPFWindow_Exiting;

			if (NativeWindow != null)
				NativeWindow.Closing -= NativeWindow_Closing;
		}
	}
}
